package io.github.ali.commons.lock.aop;

import io.github.ali.commons.beanutils.CacheableHashMap;
import io.github.ali.commons.lock.Locker;
import io.github.ali.commons.lock.LockerFactory;
import io.github.ali.commons.lock.annotation.RequireLock;
import io.github.ali.commons.lock.exception.LockException;
import io.github.ali.commons.lock.impl.NativeLocker;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.PrioritizedParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 分布式锁切面定义
 * @author bailey
 */
@Aspect
public class LockAspect implements ApplicationContextAware,InitializingBean {
	private static final String KEY_SIGN = "_";
    
    private LockerFactory lockerFactory;
	private PrioritizedParameterNameDiscoverer aliLockPrioritizedParameterNameDiscoverer;

    private CacheableHashMap<String,Locker> lockers;
	private Function<String, Locker> lockerGetter;

	public LockAspect(PrioritizedParameterNameDiscoverer aliLockPrioritizedParameterNameDiscoverer){
	    this.aliLockPrioritizedParameterNameDiscoverer = aliLockPrioritizedParameterNameDiscoverer;
    }

    public void setLockerFactory(LockerFactory lockerFactory) {
        this.lockerFactory = lockerFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (lockerFactory == null) {
//			throw new LockException("No instance of " + LockerFactory.class + " was found!");
            //未配置则默认启用本地锁
            lockerFactory = NativeLocker::new;
        }
        lockers = new CacheableHashMap<>();
        lockers.put(LockerFactory.DEFAULT_LOCKER_NAME, lockerFactory.create());
        lockerGetter = (lockerName) -> lockerFactory.create(lockerName);
    }
    
    @Pointcut("@annotation(io.github.ali.commons.lock.annotation.RequireLock)")
    public void handleLocker() {
    }

    @Around("handleLocker()")
    public Object handle(ProceedingJoinPoint pJoinPoint) throws Throwable {
        Object[] args = pJoinPoint.getArgs();
        Method method = ((MethodSignature) pJoinPoint.getSignature()).getMethod();
        String[] argsName = aliLockPrioritizedParameterNameDiscoverer.getParameterNames(method);

        RequireLock requireLock = method.getAnnotation(RequireLock.class);
		String lockerName = requireLock.lockerName();
        String key = renderKey(requireLock, method, argsName, args);
        String value = UUID.randomUUID().toString();
		Locker locker = lockers.get(lockerName,lockerGetter);
		if (locker == null) {
			throw new LockException(LockerFactory.DEFAULT_LOCKER_NAME.equals(lockerName) 
					? "No default Locker found . make sure the method 'LockerFactory.create()' return a effective locker."
					: "No Locker found by name :" + lockerName);
		}
		locker.lock(key, value, TimeUnit.SECONDS.convert(requireLock.expiring(), requireLock.timeUnit()),requireLock.tryTimes(), requireLock.tryGap());
		try {
			return pJoinPoint.proceed(args);
		} finally {
			locker.unlock(key, value);
		}
    }

    private String renderKey(RequireLock requireLock, Method method, String[] paramName, Object[] params) throws LockException{
        String keyExpression = requireLock.key();
        keyExpression = StringUtils.isEmpty(keyExpression) ? requireLock.value() : keyExpression;
        Object key = keyExpression;
        if (StringUtils.isEmpty(keyExpression)) {
            //key = ArrayUtils.isEmpty(params) ? null : BeanUtils.dump(params[0]);
            key = method.getDeclaringClass().getName() + "." + method.getName();
		} else if (keyExpression.contains("#") || keyExpression.contains("@")) {
            if (ArrayUtils.isNotEmpty(params)) {
                boolean hasMatched = false;
                Collection<String> existed = new ArrayList<>();
                ExpressionParser parser = new SpelExpressionParser();
                StandardEvaluationContext context = new StandardEvaluationContext();
				context.setBeanResolver(beanFactoryResolver);
				//初步判断keyExpression中指定的参数名是否存在于参数列表
                for (int i = 0; i < paramName.length; i++) {
                    if (!existed.contains(paramName[i])) {
                        existed.add(paramName[i]);
                        if (keyExpression.contains(paramName[i])) {
                            context.setVariable(paramName[i], params[i]);
                            hasMatched = true;
                        }
                    }
                }
                if (hasMatched) {
                    try {
                        key = parser.parseExpression(keyExpression).getValue(context, Object.class);
                    } catch (Exception e) {
                        throw new LockException("LockAspect renderKey(" + keyExpression + ") error!", e);
                    }
                }
            }
        }
		return new StringBuilder()
                    .append(requireLock.prefix())
                    .append(StringUtils.isNotEmpty(requireLock.prefix()) ? KEY_SIGN : StringUtils.EMPTY)
                    .append(key)
                    .append(StringUtils.isNotEmpty(requireLock.suffix()) ? KEY_SIGN : StringUtils.EMPTY)
                    .append(requireLock.suffix()).toString();
    }
    private BeanFactoryResolver beanFactoryResolver;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		beanFactoryResolver=new BeanFactoryResolver(applicationContext);
	}
}
